// r.editor.utils;
var UE = {};
var c;

c = UE.utils = {
  // e：一个对象或者数组；
  // t：一个函数--迭代器（当e是一个数组时，t的参数有三个：element、index和整个数组；当e是一个对象时，参数也有三个：value、key和整个对象）；
  //    t 执行后如果返回 false，表示退出迭代。
  // n：将 t 中的 this 指向n；
  each: function(e, t, n) {
    if (null != e)
      // e 是数组
      if (e.length === +e.length) {
        for (var i = 0, o = e.length; o > i; i++)
          if (t.call(n, e[i], i, e) === !1) return !1;
      } else
        // e 是对象
        for (var r in e)
          if (e.hasOwnProperty(r) && t.call(n, e[r], r, e) === !1) return !1;
  },
  // 创建一个空函数，它的prototype就是参数e，然后通过这个函数创建一个对象，最后返回这个对象；
  // 注意：虽然后来有 t.prototype=null, 但返回的对象的__proto__中已经具有了e中的方法；
  // 返回的对象 e 的 e.__proto__ = 参数e；
  makeInstance: function(e) {
    var t = new Function; // t 是个构造函数
    return t.prototype = e, e = new t, t.prototype = null, e;
  },
  // t 和 e 两个 {} 形式的对象；
  // t 中的 key 添加到 e 中，
  //    n 为 true 时重名 key 不覆盖，
  //    n 为 false 时重名 key 覆盖；
  extend: function(e, t, n) {
    if (t)
      for (var i in t) n && e.hasOwnProperty(i) || (e[i] = t[i]);
    return e;
  },
  // (obj,obj1,obj2, ...)
  // e 总是接收第一个参数，也就是 obj；
  // 把从 obj1 开始的所有参数对象的 key 都添加到 obj 上去；
  // 覆盖的情况如下：
  //    从obj到结束的这些对象中，同名的key只会取第一次出现的值。
  //    比如，obj.a 存在--即使是 undefined，那么后面的参数中如果有 a，也不会覆盖 obj.a，最后 obj.a 仍然是第一个值；
  //        同样的，obj 没有 b，obj1.b 存在--即使是 undefined，在把 obj1.b 赋值给 obj.b 前，obj.hasOwnProperty('b') 为假，所以会把 obj1.b 赋值给 obj.b；
  //        之后，obj.hasOwnProperty('b') 变成了真，那么后面的参数（从 obj2 开始）中如果有 b，也不会覆盖 obj.b，最后 obj.b 仍然是第一个值（obj1.b 的值）；
  extend2: function(e) {
    for (var t = arguments, n = 1; n < t.length; n++) {
      var i = t[n];
      for (var o in i) 
        e.hasOwnProperty(o) || (e[o] = i[o]);
    }
    return e;
  },
  // 参数 e 和 t 是两个构造函数；
  // 假设：e.prototype = {a:f}; t.prototype = {b:f}; // f 表示一个函数；
  // 结果就是：
  // 1，i是一个对象，i.__proto__ 就是 t.prototype;
  // 2，c.extend(i, n, !0) 导致 console.log(i.hasOwnProperty('a')) 是 true, ===> i = {a:f}
  // 3，e.prototype = i = {a:f}
  // 4，i.constructor = e 导致 e.prototype.constructor = e;
  // 5，所以，使用导出的构造函数e（prototype是 i）实例化对象 ee，自然可以使用 e.prototype 里的方法；
  // 6，而且，因为ee.__proto__ === e.prototype === i, 所以 ee.__proto__.__proto__ === e.prototype.__proto__ === i.__proto__ === t.prototype === {b:f}
  // 于是，ee 也可以使用 b方法。
  // 总之就是向上多层次继承；
  // 本方法最后返回的是 i.constructor，也就是 e；
  // 另外，函数作为参数是引用类型的，所以即使没有变量接收此方法的返回值，在该方法执行后，直接通过 e 实例化得到 ee，效果是一样的。、
  // ee instanceof e/t 都是 true。相当于是原型继承的经典模式，应该和 e.prototype = new t(); 差不多吧~~
  inherits: function(e, t) {
    var n = e.prototype,
      i = c.makeInstance(t.prototype);// i.__proto__ = t.prototype;

      // n 的 key 扩展到 i 上（值扩展 ownProperty），不覆盖；
      // e.prototype 里有 n（参数 e.prototype） 的 key;
      // e.prototype.__proto__ 里有 t.prototype 里的 key；
      // 自然的，通过e得到的对象，可以使用 e.prototype 和 t.prototype 里的 key。
    return c.extend(i, n, !0), e.prototype = i, i.constructor = e;
  },
  // 改变函数 e 里 this 指向 t；
  // 返回函数，该函数需要再次手动执行；
  bind: function(e, t) {
    return function() {
      return e.apply(t, arguments);
    };
  },
  /*
  function bo(){console.log(123);}
  console.log('');
  var ret = c.defer(bo, 5000);
  ret();
  ret();
  ret();
  当第三个参数为 假 时，连续打印 3 次123；
  当第三个参数为 真 时，只打印一次，这是由于前面两个的定时器的被清除（clearTimeout）了。
   */
  defer: function(e, t, n) {
    var i;
    return function() {
      n && clearTimeout(i),
      i = setTimeout(e, t);
    };
  },

  // 从数组 e 中查询 t 首次出现的索引，从索引 n 开始（默认0），若无则返回 -1。
  indexOf: function(e, t, n) {
    var i = -1;
    n = this.isNumber(n) ? n : 0, 
    this.each(e, function(e, o) {
      return o >= n && e === t ? (i = o, !1) : void 0;
    });
    return i;
  },

  // 从数组 e 中删除所有的 t（严格比较）；
  // 最后的 n-- 作用是：从数组中删除一个元素后，原数组减少了，数组长度减小，删除掉的那个元素后面的元素索引减小了，
  // 如果不 n--，可能导致有些满足条件的元素无法删除。
  // 另外，e[n] 可能会导致索引溢出，但是这个溢出倒是没有关系，值是 undefined，不会报错。关键还是确保连续的同值元素都能删除掉。
  removeItem: function(e, t) {
    for (var n = 0, i = e.length; i > n; n++) 
      e[n] === t && (e.splice(n, 1), n--);
  },

  // 删除字符串左右两侧的空格
  trim: function(e) {
    return e.replace(/(^[ \t\n\r]+)|([ \t\n\r]+$)/g, "");
  },

  // e 要么是数组，要么是',' 分隔的字符串；
  // 每个元素作为对象的 key（元素本身以及转为大写后的两个 key），值都是1。
  // ['p', 'div', 'input'] => {p:1, P:1, div:1, DIV:1, input:1, INPUT:1}
  // 'p,div,input' => {p:1, P:1, div:1, DIV:1, input:1, INPUT:1}
  listToMap: function(e) {
    if (!e) return {};
    e = c.isArray(e) ? e : e.split(",");
    for (var t, n = 0, i = {}; t = e[n++];) 
      i[t.toUpperCase()] = i[t] = 1;
    return i;
  },

  // html 字符转为字符实体
  // e 为空字符串时直接返空字符串
  // t 是指定要替换的字符串或者正则表达式
  unhtml: function(e, t) {
    return e ? e.replace(t || /[&<">'](?:(amp|lt|quot|gt|#39|nbsp);)?/g, function(e, t) {
      // t 是分组匹配的内容
      // 也就是说，如果 t 有值，说明就是匹配到了'&...;'，t 正是 & 后的内容；此时直接返回 e 即可；
      // 如果 t 没有值，说明匹配到了 <，或者>，或者"，或者'；此时则转为 html 字符实体；
      return t ? e : {
        "<": "&lt;",
        "&": "&amp;",
        '"': "&quot;",
        ">": "&gt;",
        "'": "&#39;"
      }[e];
    }) : "";
  },

  // 字符实体转html字符
  html: function(e) {
    return e ? e.replace(/&((g|l|quo)t|amp|#39);/g, function(e) {
      return {
        "&lt;": "<",
        "&amp;": "&",
        "&quot;": '"',
        "&gt;": ">",
        "&#39;": "'"
      }[e];
    }) : "";
  },

  // 将css样式转换为驼峰的形式
  // 如果是'float'，则根据浏览器返回 js 中应该使用的 cssFloat | styleFloat | float;
  // 如果是其他的样式，比如 font-size，则转为 fontSize;
  cssStyleToDomStyle: function() {
    var e = document.createElement("div").style,
      t = {
        "float": void 0 != e.cssFloat ? "cssFloat" : void 0 != e.styleFloat ? "styleFloat" : "float"
      };
    // 参数 e 是 cssName，比如"border-top"；
    return function(e) {
      // 如果参数 e 是 'float' 就直接返回 t.float 的值，否则进行 replace 操作；
      return t[e] || (
        t[e] = e.toLowerCase().replace(/-./g, function(e) {
          // 对于 'border-top'，匹配后 e 就是 '-t'
          return e.charAt(1).toUpperCase();// 返回 'borderTop'；
        })
      );
    };
  }(),

  /**
   * UE.utils.loadFile( document, {
   *     src:"test.js",
   *     tag:"script",
   *     type:"text/javascript",
   *     defer:"defer"
   * }, function () {
   *     console.log('加载成功');
   * } );
   */
  loadFile: function() {
    // getItem(doc, obj)
    // e：document；
    // n：{src, tag, type, defer, href, ...}
    function e(e, n) {
      try {
        for (var i, o = 0; i = t[o++];)
          // 如果 t 中某个元素的两个属性和要添加的对象一致，则直接返回该元素；
          if (i.doc === e && i.url == (n.src || n.href)) return i;
      } catch (r) {
        return null;
      }
    }
    
    var t = [];

    // loadFile 方法就是下面 return 的函数；
    // (doc, obj, fn)
    // n: dom 对象；
    // i: 具有src或者href属性的对象{}；
    // o: 函数；
    // 因为三次调用 n 都是 document，所以这里就把 n 当做 document 即可；
    return function(n, i, o) {
      // i.src && (i.src += "?v=" + UE.version),
      // i.href && (i.href += "?v=" + UE.version);
      var r = e(n, i);
      
      // 如果 t 中已经有了
      if (r) return void(r.ready ? o && o() : r.funs.push(o));
      
      // 如果 t 中还没有
      t.push({
        doc: n,
        url: i.src || i.href,
        funs: [o]
      });

      // 这里看不懂，n.body 不存在，n肯定不是 document，那么 n.write 还存在，n 是什么东西？
      // if (!n.body) {
      //   // 如果 n.body 不存在，n 不是 document 对象；
      //   var a = [];// 存放htmlDOM元素的属性，比如 ['src="test.js"', 'defer="defer"']；
      //   for (var s in i) 
      //     "tag" != s && a.push(s + '="' + i[s] + '"');
      //   return void n.write("<" + i.tag + " " + a.join(" ") + " ></" + i.tag + ">");
      // }

      // n.body 存在（因为三次调用 n = document，所以一定会执行这里）
      // 如果传递有 id 并且，具有该 id 的元素已经存在，直接结束了。
      // 反之，执行 if 里面的；（没有id，直接新建；或者有id但指定元素不存在，也进行新建。）
      if (!i.id || !n.getElementById(i.id)) {
        var l = n.createElement(i.tag);
        delete i.tag;
        for (var s in i) l.setAttribute(s, i[s]);
        l.onload = l.onreadystatechange = function() {
          // 新建元素加载完毕
          if (!this.readyState || /loaded|complete/.test(this.readyState)) {
            r = e(n, i);
            if (r.funs.length > 0) {
              r.ready = 1;
              for (var t; t = r.funs.pop();) t();
            }
            l.onload = l.onreadystatechange = null;
          }
        },
        l.onerror = function() {
          throw Error("【editor_all_min.js模块 loadFile方法执行】The load " + (i.href || i.src) + " fails,check the url settings of file ueditor.config.js ");
        },
        n.getElementsByTagName("head")[0].appendChild(l);
      }
    };
  }(),

  // 当不传递参数时，返回 true；
  // {},[],'' 都返回true；
  isEmptyObject: function(e) {
    if (null == e) return !0;
    if (this.isArray(e) || this.isString(e)) return 0 === e.length;
    for (var t in e)
      if (e.hasOwnProperty(t)) return !1;
    return !0;
  },

  // 颜色转为 16 进制形式
  // e：'color' / 'background-color' / ...;
  // t：rgb(255,255,255) / rgba(255,255,255);
  // rgb(255,255,255)  => "#FFFFFF"
  fixColor: function(e, t) {
    if (/color/i.test(e) && /rgba?/.test(t)) {
      var n = t.split(",");
      if (n.length > 3) return "";
      t = "#";
      for (var i, o = 0; i = n[o++];)
        // i：分别是 'rgba(255'、'255'、'255)'；
        // replace 后，变成了 255、255、255；
        i = parseInt(i.replace(/[^\d]/gi, ""), 10).toString(16),
        t += 1 == i.length ? "0" + i : i;// 处理为2位数字的16进制；
      t = t.toUpperCase();
    }
    return t;
  },

  // 只针对border,padding,margin做了处理，因为性能问题；
  // opt 可能就是优化的意思：optimize；
  // 没看到哪里用到了；
  // e：style 字符串；
  // "margin-top: 10px;   color :red; padding-bottom : 5px; border-right: initial; margin-left: 14px; padding-right: 22px; border-top : 12px; &gt;;;"
  optCss: function(e) {
    // function opt(obj, name) {
    // e：{top: 值, ...}；
    // 
    function t(e, t) {
      if (!e) return "";
      var n = e.top,
        i = e.bottom,
        o = e.left,
        r = e.right,
        a = "";
      // 比如 padding，四个方向都有，根据原 style 字符串的情况，去除一些无用的空白，各自分开写的css 合并到一个 padding上去，减少代码量。
      if (n && o && i && r) 
        a += ";" + t + ":" + (
          // 如果四个方向值相同，则 padding:值;
          // 如果 上下 值相同、并且左右值相同，则 padding:上值 左值；
          // 如果 左右 值相同、上下 值不同，则 padding:上值 左值 下值；
          // 否则四个方向分别写出，则 padding:上值 右值 下值 左值。
          n == i && i == o && o == r ? n : n == i && o == r ? n + " " + o : o == r ? n + " " + o + " " + i : n + " " + r + " " + i + " " + o
        ) + ";";
      else
        // 比如 margin，四个方向不全，此时功能是
        //    根据原 style 字符串的情况，去除一些无用的空白，如果彼此不在一起则进行聚合（当然各方向的 css 还是分开写）；
        for (var s in e) 
          a += ";" + t + "-" + s + ":" + e[s] + ";";
      return a;
    }
    var n, i;
    // n 保存 padding 相关的，{top: 值, ...}
    // i 保存 margin 相关的，{top: 值, ...}
    e = e.replace(/(padding|margin|border)\-([^:]+):([^;]+);?/gi, function(e, t, o, r) {
      // o：top、right、bottom、left 之一；
      // r：值；
      // 只处理一个值的情况；对于 'border-top: 1px solid red;' 这样的不处理。
      if (1 == r.split(" ").length) 
        switch (t) {
          case "padding":
            return !n && (n = {}), n[o] = r, "";
          case "margin":
            return !i && (i = {}), i[o] = r, "";
          case "border":
            return "initial" == r ? "" : e;
        }
      return e;
    }), 
    e += t(n, "padding") + t(i, "margin");
    // 1、去除开头的空白和';'，去除结尾的空白；
    // 2、\1 代表 ';([ \n\r\t]+)'; ';空白' 和 ';空白;' 都替换成 ';'；
    // 3、没有特殊符号的情况下，t 为假，2个及以上的';'替换成1个';'；
    //    有特殊符号，且匹配到了，比如'&gt;'，那么 t 就是 '&gt'，整个正则匹配的是 '&gt;;' || '&gt;;;;'（关键是这种） 等，替换成 '&gt;;'。
    return e.replace(/^[ \n\r\t;]*|[ \n\r\t]*$/, "").replace(/;([ \n\r\t]+)|\1;/g, ";").replace(/(&((l|g)t|quot|#39))?;{2,}/g, function(e, t) {
      return t ? t + ";;" : ";";
    });
  },

  // 克隆一个无引用关系的副本；
  // 如果没有t（{} 对象），创建一个空的对象 t，得到 e 的一个副本；
  // 如果有了t（{} 对象），把 e 覆盖到 t 上（同名的会进行覆盖）；
  clone: function(e, t) {
    var n;
    t = t || {};
    for (var i in e)
      e.hasOwnProperty(i) && (
        n = e[i],
        // n 如果是基础数据类型，直接赋值给 t[i]；
        // n 如果是 Object 或者 Array，则递归进行覆盖；
        "object" == typeof n ? (
          t[i] = c.isArray(n) ? [] : {},
          c.clone(e[i], t[i]) // 递归
        ) : t[i] = n
      );
    return t;
  },

  // 把cm／pt为单位的值转换为px为单位的值
  // 显示器分辨率 72 dpi；28.68525896414343
  transUnitToPx: function(e) {
    // 如果 e 中没有 pt 和 cm，直接返回 e；
    if (!/(pt|cm)/.test(e)) return e;

    var t;
    e.replace(/([\d.]+)(\w+)/, function(n, i, o) {
      e = i, t = o;
      // e 是数值，o 是单位
    });
    switch (t) {
      case "cm":
        e = 25 * parseFloat(e); // 1cm 有 25 个px
        break;
      case "pt":
        e = Math.round(96 * parseFloat(e) / 72);
    }
    return e + (e ? "px" : "");
  },

  // DomReady方法，回调函数将在dom树ready完成后执行
  // 只在本模块的下面用了一次，只传递了第一个参数；
  domReady: function() {
    // 设置 document.isReady = true; 依次执行 DOMReady(fn) 中的fn；
    function e(e) {
      e.isReady = !0;
      for (var n; n = t.pop(); n());
    }
    var t = [];

    return function(n, i) {
      // console.error('UE.utils.domReady return function');
      function o() {
        r.removeEventListener("DOMContentLoaded", o, !1), 
        e(r);
      }

      i = i || window;

      // alert('window.top === window.self');
      // alert(window.top === window.self);

      var r = i.document;
      n && t.push(n),
      "complete" === r.readyState || "loading" !== document.readyState && !document.documentElement.doScroll ? e(r) : (
        r.isReady && e(r),
        i.addEventListener ? (
          r.addEventListener("DOMContentLoaded", o, !1),
          i.addEventListener("load", function() {
            e(r);
          }, !1)
        ) : (
          !function() {
            if (!r.isReady) {
              try {
                r.documentElement.doScroll("left");
              } catch (t) {
                return void setTimeout(arguments.callee, 0);
              }
              e(r);
            }
          }(),

          i.attachEvent("onload", function() {
            e(r);
          })
        )
      );
    };// return 结束；    
  }(),
  
  
  // 动态添加css样式
  // 添加、获取、清空；
  cssRule: s.ie && 11 != s.version && document.createStyleSheet ? function(e, t, n) {
    // console.error('UE.utils.cssRule IE');
    var i, o;
    n = n || document, 
    i = n.indexList ? n.indexList : n.indexList = {};
    var r;
    if (i[e]) r = n.styleSheets[i[e]];
    else {
      if (void 0 === t) return "";
      r = n.createStyleSheet("", o = n.styleSheets.length),
      i[e] = o;
    }
    return void 0 === t ? r.cssText : void(r.cssText = t || "");
  } : function(e, t, n) {
    // console.error('UE.utils.cssRule 非IE');
    n = n || document;
    var i, o = n.getElementsByTagName("head")[0];
    if (!(i = n.getElementById(e))) {
      if (void 0 === t) return "";
      i = n.createElement("style"), 
      i.id = e, 
      o.appendChild(i);
    }
    return void 0 === t ? i.innerHTML : void("" !== t ? i.innerHTML = t : o.removeChild(i));
  },
  
  // 数组排序，升序
  sort: function(e, t) {
    t = t || function(e, t) {
      return e.localeCompare(t);
    };
    for (var n = 0, i = e.length; i > n; n++)
      for (var o = n, r = e.length; r > o; o++)
        if (t(e[n], e[o]) > 0) {
          var a = e[n];
          e[n] = e[o], e[o] = a;
        }
    return e;
  }
};

c.each(["String", "Function", "Array", "Number", "RegExp", "Object"], function(e) {
  UE.utils["is" + e] = function(t) {
    return Object.prototype.toString.apply(t) == "[object " + e + "]";
  };
});
// UE.utils 结束